package com.ds.lens.collector.receive.handler;


import com.google.common.base.Charsets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import static com.ds.lens.collector.receive.constants.LensConstants.LENS_APP_KEY;
import static com.ds.lens.collector.receive.constants.LensConstants.LENS_MESSAGE_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * @author ethan
 * @author WeiShaoying
 */
@Slf4j
public class HttpServerInboundHandler extends SimpleChannelInboundHandler {

    private static final String ACCESS_CONTROL_ALLOW_HEADER_STR = "x-requested-with,content-type" +
            "," + LENS_APP_KEY +
            "," + LENS_MESSAGE_TYPE;

    private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";

    private HttpRequest request;

    private HandlerFactory handlerFactory;

    private String appKey = null;
    private String appId = null;
    private String type = null;
    private ByteBuf byteBuf = null;


    public HttpServerInboundHandler(HandlerFactory handlerFactory) {
        this.handlerFactory = handlerFactory;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpRequest) {
            processHttpHeader(ctx, (HttpRequest) msg);
        } else if (msg instanceof HttpContent) {
            processHttpBody(ctx, (HttpContent) msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(cause.getMessage());
    }

    private void processHttpHeader(ChannelHandlerContext ctx, HttpRequest httpRequestMessage) {
        request = httpRequestMessage;
        HttpHeaders httpHeaders = request.headers();
        String accessControlHeader = httpHeaders.get(ACCESS_CONTROL_REQUEST_HEADERS);
        if (accessControlHeader != null) {
            return;
        }
        appKey = httpHeaders.get(LENS_APP_KEY);
        type = httpHeaders.get(LENS_MESSAGE_TYPE);
        if (StringUtils.isEmpty(appKey)) {
            log.warn("Illegal request, request header invalid.");
            ctx.close();
            return;
        }
        byteBuf = null;
        Integer contentLength = httpHeaders.getInt(CONTENT_LENGTH);
        if (contentLength != null) {
            byteBuf = Unpooled.buffer(contentLength.intValue());
        }
    }

    private void processHttpBody(ChannelHandlerContext ctx, HttpContent httpContentMessage) {
        ByteBuf currentContent = httpContentMessage.content();
        if (currentContent.isReadable() && byteBuf != null) {
            byteBuf.writeBytes(currentContent);
        }
        if (httpContentMessage instanceof LastHttpContent) {
            byte[] dataArray = null;
            if (byteBuf != null) {
                dataArray = byteBuf.array();
            }
            handleStandardRequest(ctx, dataArray);
        }
    }

    private void handleStandardRequest(ChannelHandlerContext ctx, byte[] dataArray) {
        HandlerResultWrapper handlerResultWrapper = null;
        try {
            if (StringUtils.isEmpty(appKey)) {
                handlerResultWrapper = getHandlerResultWrapper("No Lens-App-Key");
                return;
            }
            Handler handler = handlerFactory.getHandler(type);
            if (handler == null) {
                log.warn("Illegal request, request handler not found.");
                handlerResultWrapper = getHandlerResultWrapper("Illegal request");
                return;
            }
            RequestInfo.RequestInfoBuilder requestInfoBuilder = RequestInfo.builder().appKey(appKey);
            handlerResultWrapper = handler.handle(new String(dataArray, Charsets.UTF_8), requestInfoBuilder.build());
        } catch (Exception e) {
            log.error("process request failed.", e);
            handlerResultWrapper = getHandlerResultWrapper("Process request failed.");
        } finally {
            responseRequest(ctx, handlerResultWrapper);
        }
    }

    private HandlerResultWrapper getHandlerResultWrapper(String s) {
        HandlerResultWrapper handlerResultWrapper = new HandlerResultWrapper();
        handlerResultWrapper.setResult(s);
        return handlerResultWrapper;
    }

    private void responseRequest(ChannelHandlerContext ctx, HandlerResultWrapper resultWrapper) {
        try {
            byte[] result = resultWrapper == null ? "".getBytes() : resultWrapper.getResult().getBytes(Charsets.UTF_8);
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(result));
            HttpHeaders httpHeaders = response.headers();
            if (!StringUtils.isEmpty(appKey)) {
                httpHeaders.set(LENS_APP_KEY, appKey);
            }
            httpHeaders.set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
            httpHeaders.set(ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_HEADER_STR);
            httpHeaders.set(CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
            httpHeaders.set(CONTENT_LENGTH, response.content().readableBytes());
            ctx.write(response);

        } catch (Exception e) {
            log.error("write response error!", e);
        }
    }

    private FullHttpResponse createFullHttpResponseByString(String result) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        if (null != result) {
            byte[] dataBytes = result.getBytes();
            response = response.replace(Unpooled.copiedBuffer(dataBytes));
        }

        response.headers().set(request.headers());
        //response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response.headers().set(ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_HEADER_STR);
        response.headers().set(CONTENT_TYPE, "text/plain");
        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
        return response;
    }
}
